Sessions

Sessions overview

The notion of Session is close to DataContext in ADO.NET Entity Framework.

Key facts about sessions:

  • Session is associated with single IDbConnection object that is used to access the underlying storage.
  • Session is created by Domain.OpenSession/Domain.OpenSessionAsync method. A session always belongs to the domain where it was created, and consequently allows to operate with (and only with) types/services, which are registered in that Domain.
  • Instances of persistent types always belong to (bound to) a session.
  • Session maintains its own cache of fetched entities, which is used and updated completely transparently.
  • Session maintains IdentityMap that provides entity uniqueness.
  • Session tracks changes in its entities and executes required insert, modify & remove operations on Persist.
  • Sessions aren’t safe for the multithreaded operations. To work with a storage in multiple threads you should use separate session for each thread, or manually synchronize access to them.

Session implements IDisposable interface so it is convenient to use it within using construct.

using (var session = domain.OpenSession()) {

  // Work with persistent classes here
}

or

await using (var session = await domain.OpenSessionAsync()) {

  // Work with persistent classes here
}

Session profiles

Starting from DataObjects.Net 4.4 ‘Automatic Session activation’ mode made optional, moreover, it is switched off by default. In addition, Session Profile notion is introduced. It represents a combination of predefined session options for particular scenarios.

For now there are three main profiles: Server profile, Client profile & Legacy profile.

  • Server profile is intended to be used in server scenarios, such as ASP.NET, ASP.NET MVC, WCF Data Services, etc. It doesn’t include ‘Automatic Session activation’ mode, so several Session instances could be used within a thread and all actions with Entities, Structures & EntitySets require the presence of opened Transaction. This profile is the default one.
  • Client profile is designed for WPF, Windows Forms, Console applications, whatever. It uses the new ‘Disconnected’ Session mode which means that Session runs in occasionally connected conditions, logs modifications of Entities and applies them to a database on request. It doesn’t include ‘Automatic Session activation’ either. Transations are not required but allowed (in this mode it uses local in-memory transactions, not database ones, in order to log Entity modifications).
  • To preserve compatibility with Session behavior from old versions of DataObjects.Net (4.3 and earlier) a special profile named Legacy is added, which has ‘Automatic Session activation’ switched on.

Session profiles usage

Session profile is a combination of SessionOptions values, so we build SessionConfiguration instance using this profile and construct Session afterwards.

var configuration = new SessionConfiguration(
    "mySession", SessionOptions.ClientProfile);
using (var session = domain.OpenSession(configuration)) {
   // some code
}

In case you want every Session within your Domain runs in a particular profile by default, you can set up Default Session in applicatin configuration file:

<domain upgradeMode="..." connectionUrl="...">
  <types>
    <add assembly="..."/>
  </types>
  <sessions>
    <session name="Default" options="ClientProfile" />
  </sessions>
</domain>

You may achieve the same by setting up Default Session in C# code:

var domainConfiguration = new DomainConfiguration();
var sessionConfiguration = new SessionConfiguration(
    "Default", SessionOptions.ClientProfile);
domainConfiguration.Sessions.Add(sessionConfiguration);
var domain = Domain.Build(domainConfiguration);

Here, Session name Default is a name of one of built-in predefined session configurations. When you manually setup a session configuration with name Default, DataObjects.Net uses yours instead of predefined one.

There are other predefined session configurations with following built-in names:

  • System configuration is used for opening session during Domain building. Upgrade actions (both structure and data actions plus everything is done in UpgradeHandler.OnStage and UpgradeHandler.OnUpgrade methods) are performed within System session.
  • KeyGenerator configuration is used in cases when storage requires a separate session for getting a keys portion from table- or sequence-based generator.

When overriding one of non-Default configuration MAKE SURE you UNDERSTAND what you’re changing. Some of the options are pretty safe to change, like DefaultCommandTimeout, CacheSize, BatchSize, others - can cause problems when changed carelessly, like DefaultIsolationLevel.

Configuring sessions

Another method overload domain.OpenSession(sessionConfig) accepts SessionConfiguration instance, that contains several session-level options:

  • CacheSize – size of the entity cache, default value is 16384 entities.
  • CacheTypeLruWeak (default value) or Infinite.
  • DefaultIsolationLevel for transactions, isolation level can be also specified when a particular transaction is being opened.
  • DefaultCommandTimeout - default timeout for each DbCommand executed within session. Default value is null, which means it is up to RDBMS settings
  • BatchSize – maximal size of SQL statement batches, this affects create, update, delete operations and future queries. This is a recommendation, not a strict value. In some cases batch can contain less SQL statemens to prevent command parameters number overflow (maximum parameters per query depends on provider).
  • Options – session options flag.
  • ReaderPreloading - specifies policy for dealing with data readers. Possible values are Auto (default value), Always, Never. In Auto DataObjects.Net tries to read directly from open DbReader and materializes results on each row enumeration if provider supports it (there is MARS support). Always option forces DataObjects.Net to preload results to memory before returning of first value (this increases memory usage but allows to emumerate several query results one during another when MARS is not supported). Never option does the oposite, it forses DataObjects.Net always use open reader and do not preload results in any case (be careful with this, some RDBMSs don’t support MARS and option can cause exceptions).
  • EntityChangeRegistry - specifies how many changed Entities registry can carry before auto persist them. Default value is 250.
  • ServiceContainerType - in case you want to implement your own IServiceContainer for session services you can set Type of your implementation instead of default one.

Session configuration can be created manually:

var sessionCongfig = new SessionConfiguration {
  BatchSize = 25,
  DefaultIsolationLevel = IsolationLevel.ReadCommitted,
  CacheSize = 1000,
  Options = SessionOptions.ServerProfile
};

Or declared and loaded from configuration file:

<domain name="TestDomain"
        upgradeMode="Recreate"
        connectionUrl="sqlserver://localhost/MyTests" >
  <sessions>
    <session name="TestSession"
             batchSize="25" isolationLevel="ReadCommitted"
             cacheSize="1000" options="AutoShortenTransactions" />
  </sessions>
</domain>
var domainConfig = DomainConfiguration.Load("TestDomain");
var sessionConfig = domainConfig.Sessions["TestSession"];

When session configuration is created or loaded, session with this configuration can be opened by special Open() method overload:

using (var session = domain.OpenSession(sessionConfig)) {
  // ...
}

or

using (var session = await domain.OpenSessionAsync(sessionConfig)) {
  // ...
}

Current session, session activation

Note

This chapter describes session particularities in DataObjects.Net with SessionProfile = Legacy or SessionOption.AutoActivation option turned on. We do not recommend using session activation in applications with paralled or asynchronous execution because it has some caviars and wrong use of it can cause problems.

In DataObjects.Net 6 session activation has changed and uses AsyncLocal instead of ThreadStatic to make session activation compatible with asychronous execution flow.

Session class implements IContext interface, it means that a session can be either active or not active in particular execution flow. In case of synchronous execution this means that active session is bound to one thread. When parallel or asyncronous execution comes to the game active session may be also available to some other threads. This is normal AsyncLocal behavior.

Each execution flow can contain only one active session, that can be a accessed via Session.Current property or Session.Demand() method. When you creates new entitiy or loads it from database, they are automatically bounded to the current session.

New session can be opened and activated by domain.OpenSession() method. In the following example newly opened session is automatically activated within the using block:

using (var session = domain.OpenSession()) {

  // creating new entity
  var newPerson = new Person();

  // fetching entity by its ID
  var fetchedPerson = session.Query.Single<Person>(personId);

  Console.WriteLine("Our session is current: {0}",
    Session.Current == session);

  Console.WriteLine("New entity is bound to our session: {0}",
    newPerson.Session == session);

  Console.WriteLine("Fetched entity is bound to our session: {0}",
    fetchedPerson.Session == session);
}

The result will be true in all cases.

Existing session can be activated by Activate() method:

using (session.Activate()) {

  // Work with persistent classes here
}

DataObjects.Net provides the following session activation pattern:

  • Session.Activate binds a session to the execution flow.
  • Session.Deactivate unbinds currently active session from the current execution flow.
  • Active (or current) session is session that was bound to the current execution flow most recently, but not yet deactivated.
  • All above implies each execution flow maintains its own stack of activated sessions. When activation happens on a particular execution flow, a new session is added to the top of its stack; this session becomes active (current) in this execution flow. When a particular session is deactivated, the session below it on the stack becomes active (current). If deactivated session is not the topmost session on the stack, all the above sessions are deactivated implicitely.

Session activation API:

  • Session.Current returns active (current) Session bound to the current execution flow; if there is no active session, it returns null.
  • Session.Demand() returns active session, if it exists. If there is no active session, it throws InvalidOperationException.
  • Session.Activate() activates the session. This method returns IDisposable object that must be disposed to deactivate the session. So normally Session.Activate() must be used inside using block.

That’s true:

  • Moreover, by default any public method (or property accessor) of any SessionBound descendant is decorated by aspect activating its SessionBound.Session for the duration of execution of this method.

So since all the objects requiring a Session (e.g. Entity, Structure and EntitySet<T>) are SessionBound descendants, almost all of their public methods implicitly activate the Session they’re bound to. This ensures:

  • You can use Session.Demand() and Session.Current inside their code, or inside any code they invoke.

  • So e.g. you can invoke static methods without passing the Session to them directly.

    Note

    Is the session activation technique fast? For example, Session.Activate() always returns an IDisposable object – so does this mean any SessionBound method call allocates something on heap?

No, it doesn’t: actually, Session.Activate() returns a special IDisposable singleton (so-called void scope) doing nothing on its disposal, if this Session is already active. So this is pretty fast process.

Current Domain

Presence of current Session allows us to provide current Domain as well:

  • Domain.Current is actually based on Session.Current;
  • The same is true for Domain.Demand().

Here is actual Domain.Current property code:

public static Domain Current {
  get {
    var session = Session.Current;
    return session != null ? session.Domain : null;
  }
}

Session Events

Session type exposes wide range of events, from Entity-related events to DbCommand events. Session events are accessed through Session.Events property. Unlike ‘event-like’ protected virtual methods in Entity & EntitySet types, session events are preferred in case you don’t have access to source code of the domain model or going to be less invasive and more flexible.

Misc events

  • EventHandler<DbCommandEventArgs> DbCommandExecuting
    Occurs when DbCommand is about to execute.
  • EventHandler<DbCommandEventArgs> DbCommandExecuted
    Occurs when DbCommand is executed.
  • EventHandler<KeyEventArgs> KeyGenerated
    Occurs when local Key created.